/****************************************************************************
 *
 * Copyright 2016 Samsung Electronics All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 ****************************************************************************/
/****************************************************************************
 * drivers/usbdev/pl2303.c
 *
 *   Copyright (C) 2008-2013, 2015 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * This logic emulates the Prolific PL2303 serial/USB converter
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>
#include <queue.h>
#include <debug.h>

#include <tinyara/kmalloc.h>
#include <tinyara/arch.h>
#include <tinyara/serial/serial.h>
#include <tinyara/usb/usb.h>
#include <tinyara/usb/usbdev.h>
#include <tinyara/usb/usbdev_trace.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

/* Number of requests in the write queue */

#ifndef CONFIG_PL2303_NWRREQS
#define CONFIG_PL2303_NWRREQS 4
#endif

/* Number of requests in the read queue */

#ifndef CONFIG_PL2303_NRDREQS
#define CONFIG_PL2303_NRDREQS 4
#endif

/* Logical endpoint numbers / max packet sizes */

#ifndef CONFIG_PL2303_EPINTIN
#warning "EPINTIN not defined in the configuration"
#define CONFIG_PL2303_EPINTIN 1
#endif

#ifndef CONFIG_PL2303_EPBULKOUT
#warning "EPBULKOUT not defined in the configuration"
#define CONFIG_PL2303_EPBULKOUT 2
#endif

#ifndef CONFIG_PL2303_EPBULKIN
#warning "EPBULKIN not defined in the configuration"
#define CONFIG_PL2303_EPBULKIN 3
#endif

/* Packet and request buffer sizes */

#ifndef CONFIG_PL2303_EP0MAXPACKET
#define CONFIG_PL2303_EP0MAXPACKET 64
#endif

/* Ideally, the BULKOUT request size should *not* be the same size as the
 * maxpacket size.  That is because IN transfers of exactly the maxpacket
 * size will be followed by a NULL packet.  The BULKOUT request buffer
 * size, on the other hand, is always the same as the maxpacket size.
 */

#ifndef CONFIG_PL2303_BULKIN_REQLEN
#define CONFIG_PL2303_BULKIN_REQLEN 96
#endif

/* Vendor and product IDs and strings */

#ifndef CONFIG_PL2303_VENDORID
#define CONFIG_PL2303_VENDORID  0x067b
#endif

#ifndef CONFIG_PL2303_PRODUCTID
#define CONFIG_PL2303_PRODUCTID 0x2303
#endif

#ifndef CONFIG_PL2303_VENDORSTR
#warning "No Vendor string specified"
#define CONFIG_PL2303_VENDORSTR  "NuttX"
#endif

#ifndef CONFIG_PL2303_PRODUCTSTR
#warning "No Product string specified"
#define CONFIG_PL2303_PRODUCTSTR "USBdev Serial"
#endif

#undef CONFIG_PL2303_SERIALSTR
#define CONFIG_PL2303_SERIALSTR "0"

#undef CONFIG_PL2303_CONFIGSTR
#define CONFIG_PL2303_CONFIGSTR "Bulk"

/* USB Controller */

#ifdef CONFIG_USBDEV_SELFPOWERED
#define PL2303_SELFPOWERED USB_CONFIG_ATTR_SELFPOWER
#else
#define PL2303_SELFPOWERED (0)
#endif

#ifdef CONFIG_USBDEV_REMOTEWAKEUP
#define PL2303_REMOTEWAKEUP USB_CONFIG_ATTR_WAKEUP
#else
#define PL2303_REMOTEWAKEUP (0)
#endif

#ifndef CONFIG_USBDEV_MAXPOWER
#define CONFIG_USBDEV_MAXPOWER 100
#endif

/* Descriptors ****************************************************************/

/* These settings are not modifiable via the TinyAra configuration */

#define PL2303_VERSIONNO           (0x0202)	/* Device version number */
#define PL2303_CONFIGIDNONE        (0)	/* Config ID means to return to address mode */
#define PL2303_CONFIGID            (1)	/* The only supported configuration ID */
#define PL2303_NCONFIGS            (1)	/* Number of configurations supported */
#define PL2303_INTERFACEID         (0)
#define PL2303_ALTINTERFACEID      (0)
#define PL2303_NINTERFACES         (1)	/* Number of interfaces in the configuration */
#define PL2303_NENDPOINTS          (3)	/* Number of endpoints in the interface  */

/* Endpoint configuration */

#define PL2303_EPINTIN_ADDR        (USB_DIR_IN|CONFIG_PL2303_EPINTIN)
#define PL2303_EPINTIN_ATTR        (USB_EP_ATTR_XFER_INT)
#define PL2303_EPINTIN_MXPACKET    (10)

#define PL2303_EPOUTBULK_ADDR      (CONFIG_PL2303_EPBULKOUT)
#define PL2303_EPOUTBULK_ATTR      (USB_EP_ATTR_XFER_BULK)

#define PL2303_EPINBULK_ADDR       (USB_DIR_IN|CONFIG_PL2303_EPBULKIN)
#define PL2303_EPINBULK_ATTR       (USB_EP_ATTR_XFER_BULK)

/* String language */

#define PL2303_STR_LANGUAGE        (0x0409)	/* en-us */

/* Descriptor strings */

#define PL2303_MANUFACTURERSTRID   (1)
#define PL2303_PRODUCTSTRID        (2)
#define PL2303_SERIALSTRID         (3)
#define PL2303_CONFIGSTRID         (4)

/* Buffer big enough for any of our descriptors */

#define PL2303_MXDESCLEN           (64)
#define PL2303_MAXSTRLEN           (PL2303_MXDESCLEN-2)

/* Vender specific control requests *******************************************/

#define PL2303_CONTROL_TYPE        (0x20)
#define PL2303_SETLINEREQUEST      (0x20)	/* OUT, Recipient interface */
#define PL2303_GETLINEREQUEST      (0x21)	/* IN, Recipient interface */
#define PL2303_SETCONTROLREQUEST   (0x22)	/* OUT, Recipient interface */
#define PL2303_BREAKREQUEST        (0x23)	/* OUT, Recipient interface */

/* Vendor read/write */

#define PL2303_RWREQUEST_TYPE      (0x40)
#define PL2303_RWREQUEST           (0x01)	/* IN/OUT, Recipient device */

/* Misc Macros ****************************************************************/

/* min/max macros */

#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif

#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif

/* Trace values *************************************************************/

#define PL2303_CLASSAPI_SETUP       TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SETUP)
#define PL2303_CLASSAPI_SHUTDOWN    TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SHUTDOWN)
#define PL2303_CLASSAPI_ATTACH      TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_ATTACH)
#define PL2303_CLASSAPI_DETACH      TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_DETACH)
#define PL2303_CLASSAPI_IOCTL       TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_IOCTL)
#define PL2303_CLASSAPI_RECEIVE     TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RECEIVE)
#define PL2303_CLASSAPI_RXINT       TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXINT)
#define PL2303_CLASSAPI_RXAVAILABLE TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXAVAILABLE)
#define PL2303_CLASSAPI_SEND        TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SEND)
#define PL2303_CLASSAPI_TXINT       TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXINT)
#define PL2303_CLASSAPI_TXREADY     TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXREADY)
#define PL2303_CLASSAPI_TXEMPTY     TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXEMPTY)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Container to support a list of requests */

struct pl2303_req_s {
	FAR struct pl2303_req_s *flink;	/* Implements a singly linked list */
	FAR struct usbdev_req_s *req;	/* The contained request */
};

/* This structure describes the internal state of the driver */

struct pl2303_dev_s {
	FAR struct uart_dev_s serdev;	/* Serial device structure */
	FAR struct usbdev_s *usbdev;	/* usbdev driver pointer */

	uint8_t config;				/* Configuration number */
	uint8_t nwrq;				/* Number of queue write requests (in reqlist) */
	uint8_t nrdq;				/* Number of queue read requests (in epbulkout) */
	bool rxenabled;				/* true: UART RX "interrupts" enabled */
	uint8_t linest[7];			/* Fake line status */
	int16_t rxhead;				/* Working head; used when rx int disabled */

	FAR struct usbdev_ep_s *epintin;	/* Interrupt IN endpoint structure */
	FAR struct usbdev_ep_s *epbulkin;	/* Bulk IN endpoint structure */
	FAR struct usbdev_ep_s *epbulkout;	/* Bulk OUT endpoint structure */
	FAR struct usbdev_req_s *ctrlreq;	/* Control request */
	struct sq_queue_s reqlist;	/* List of write request containers */

	/* Pre-allocated write request containers.  The write requests will
	 * be linked in a free list (reqlist), and used to send requests to
	 * EPBULKIN; Read requests will be queued in the EBULKOUT.
	 */

	struct pl2303_req_s wrreqs[CONFIG_PL2303_NWRREQS];
	struct pl2303_req_s rdreqs[CONFIG_PL2303_NWRREQS];

	/* Serial I/O buffers */

	char rxbuffer[CONFIG_PL2303_RXBUFSIZE];
	char txbuffer[CONFIG_PL2303_TXBUFSIZE];
};

/* The internal version of the class driver */

struct pl2303_driver_s {
	struct usbdevclass_driver_s drvr;
	FAR struct pl2303_dev_s *dev;
};

/* This is what is allocated */

struct pl2303_alloc_s {
	struct pl2303_dev_s dev;
	struct pl2303_driver_s drvr;
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Transfer helpers *********************************************************/

static uint16_t usbclass_fillrequest(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen);
static int usbclass_sndpacket(FAR struct pl2303_dev_s *priv);
static inline int usbclass_recvpacket(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen);

/* Request helpers *********************************************************/

static struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep, uint16_t len);
static void usbclass_freereq(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req);

/* Configuration ***********************************************************/

static int usbclass_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc);
#ifdef CONFIG_USBDEV_DUALSPEED
static void usbclass_mkepbulkdesc(const struct usb_epdesc_s *indesc, uint16_t mxpacket, struct usb_epdesc_s *outdesc);
static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type);
#else
static int16_t usbclass_mkcfgdesc(uint8_t *buf);
#endif
static void usbclass_resetconfig(FAR struct pl2303_dev_s *priv);
static int usbclass_setconfig(FAR struct pl2303_dev_s *priv, uint8_t config);

/* Completion event handlers ***********************************************/

static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req);
static void usbclass_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req);
static void usbclass_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req);

/* USB class device ********************************************************/

static int usbclass_bind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev);
static void usbclass_unbind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev);
static int usbclass_setup(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev, FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout, size_t outlen);
static void usbclass_disconnect(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev);
#ifdef CONFIG_SERIAL_REMOVABLE
static void usbclass_suspend(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev);
static void usbclass_resume(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev);
#endif

/* Serial port *************************************************************/

static int usbser_setup(FAR struct uart_dev_s *dev);
static void usbser_shutdown(FAR struct uart_dev_s *dev);
static int usbser_attach(FAR struct uart_dev_s *dev);
static void usbser_detach(FAR struct uart_dev_s *dev);
static void usbser_rxint(FAR struct uart_dev_s *dev, bool enable);
static void usbser_txint(FAR struct uart_dev_s *dev, bool enable);
static bool usbser_txempty(FAR struct uart_dev_s *dev);

/****************************************************************************
 * Private Variables
 ****************************************************************************/

/* USB class device ********************************************************/

static const struct usbdevclass_driverops_s g_driverops = {
	usbclass_bind,				/* bind */
	usbclass_unbind,			/* unbind */
	usbclass_setup,				/* setup */
	usbclass_disconnect,		/* disconnect */
#ifdef CONFIG_SERIAL_REMOVABLE
	usbclass_suspend,			/* suspend */
	usbclass_resume,			/* resume */
#else
	NULL,						/* suspend */
	NULL,						/* resume */
#endif
};

/* Serial port *************************************************************/

static const struct uart_ops_s g_uartops = {
	usbser_setup,				/* setup */
	usbser_shutdown,			/* shutdown */
	usbser_attach,				/* attach */
	usbser_detach,				/* detach */
	NULL,						/* ioctl */
	NULL,						/* receive */
	usbser_rxint,				/* rxinit */
	NULL,						/* rxavailable */
#ifdef CONFIG_SERIAL_IFLOWCONTROL
	NULL,						/* rxflowcontrol */
#endif
	NULL,						/* send */
	usbser_txint,				/* txinit */
	NULL,						/* txready */
	usbser_txempty				/* txempty */
};

/* USB descriptor templates these will be copied and modified **************/

static const struct usb_devdesc_s g_devdesc = {
	USB_SIZEOF_DEVDESC,			/* len */
	USB_DESC_TYPE_DEVICE,		/* type */
	{LSBYTE(0x0200), MSBYTE(0x0200)},	/* usb */
	USB_CLASS_PER_INTERFACE,	/* classid */
	0,							/* subclass */
	0,							/* protocol */
	CONFIG_PL2303_EP0MAXPACKET,	/* maxpacketsize */
	{
		LSBYTE(CONFIG_PL2303_VENDORID),	/* vendor */
		MSBYTE(CONFIG_PL2303_VENDORID)
	},
	{
		LSBYTE(CONFIG_PL2303_PRODUCTID),	/* product */
		MSBYTE(CONFIG_PL2303_PRODUCTID)
	},
	{
		LSBYTE(PL2303_VERSIONNO),	/* device */
		MSBYTE(PL2303_VERSIONNO)
	},
	PL2303_MANUFACTURERSTRID,	/* imfgr */
	PL2303_PRODUCTSTRID,		/* iproduct */
	PL2303_SERIALSTRID,			/* serno */
	PL2303_NCONFIGS				/* nconfigs */
};

static const struct usb_cfgdesc_s g_cfgdesc = {
	USB_SIZEOF_CFGDESC,			/* len */
	USB_DESC_TYPE_CONFIG,		/* type */
	{0, 0},						/* totallen -- to be provided */
	PL2303_NINTERFACES,			/* ninterfaces */
	PL2303_CONFIGID,			/* cfgvalue */
	PL2303_CONFIGSTRID,			/* icfg */
	USB_CONFIG_ATTR_ONE |		/* attr */
	PL2303_SELFPOWERED | PL2303_REMOTEWAKEUP,
	(CONFIG_USBDEV_MAXPOWER + 1) / 2	/* mxpower */
};

static const struct usb_ifdesc_s g_ifdesc = {
	USB_SIZEOF_IFDESC,			/* len */
	USB_DESC_TYPE_INTERFACE,	/* type */
	0,							/* ifno */
	0,							/* alt */
	PL2303_NENDPOINTS,			/* neps */
	USB_CLASS_VENDOR_SPEC,		/* classid */
	0,							/* subclass */
	0,							/* protocol */
	PL2303_CONFIGSTRID			/* iif */
};

static const struct usb_epdesc_s g_epintindesc = {
	USB_SIZEOF_EPDESC,			/* len */
	USB_DESC_TYPE_ENDPOINT,		/* type */
	PL2303_EPINTIN_ADDR,		/* addr */
	PL2303_EPINTIN_ATTR,		/* attr */
	{
		LSBYTE(PL2303_EPINTIN_MXPACKET),	/* maxpacket */
		MSBYTE(PL2303_EPINTIN_MXPACKET)
	},
	1							/* interval */
};

static const struct usb_epdesc_s g_epbulkoutdesc = {
	USB_SIZEOF_EPDESC,			/* len */
	USB_DESC_TYPE_ENDPOINT,		/* type */
	PL2303_EPOUTBULK_ADDR,		/* addr */
	PL2303_EPOUTBULK_ATTR,		/* attr */
	{LSBYTE(64), MSBYTE(64)},	/* maxpacket -- might change to 512 */
	0							/* interval */
};

static const struct usb_epdesc_s g_epbulkindesc = {
	USB_SIZEOF_EPDESC,			/* len */
	USB_DESC_TYPE_ENDPOINT,		/* type */
	PL2303_EPINBULK_ADDR,		/* addr */
	PL2303_EPINBULK_ATTR,		/* attr */
	{LSBYTE(64), MSBYTE(64)},	/* maxpacket -- might change to 512 */
	0							/* interval */
};

#ifdef CONFIG_USBDEV_DUALSPEED
static const struct usb_qualdesc_s g_qualdesc = {
	USB_SIZEOF_QUALDESC,		/* len */
	USB_DESC_TYPE_DEVICEQUALIFIER,	/* type */
	{LSBYTE(0x0200), MSBYTE(0x0200)},	/* USB */
	USB_CLASS_VENDOR_SPEC,		/* classid */
	0,							/* subclass */
	0,							/* protocol */
	CONFIG_PL2303_EP0MAXPACKET,	/* mxpacketsize */
	PL2303_NCONFIGS,			/* nconfigs */
	0,							/* reserved */
};
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/************************************************************************************
 * Name: usbclass_fillrequest
 *
 * Description:
 *   If there is data to send it is copied to the given buffer.  Called either
 *   to initiate the first write operation, or from the completion interrupt handler
 *   service consecutive write operations.
 *
 * NOTE: The USB serial driver does not use the serial drivers uart_xmitchars()
 *   API.  That logic is essentially duplicated here because unlike UART hardware,
 *   we need to be able to handle writes not byte-by-byte, but packet-by-packet.
 *   Unfortunately, that decision also exposes some internals of the serial driver
 *   in the following.
 *
 ************************************************************************************/

static uint16_t usbclass_fillrequest(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen)
{
	FAR uart_dev_t *serdev = &priv->serdev;
	FAR struct uart_buffer_s *xmit = &serdev->xmit;
	irqstate_t flags;
	uint16_t nbytes = 0;

	/* Disable interrupts */

	flags = enter_critical_section();

	/* Transfer bytes while we have bytes available and there is room in the request */

	while (xmit->head != xmit->tail && nbytes < reqlen) {
		*reqbuf++ = xmit->buffer[xmit->tail];
		nbytes++;

		/* Increment the tail pointer */

		if (++(xmit->tail) >= xmit->size) {
			xmit->tail = 0;
		}
	}

	/* When all of the characters have been sent from the buffer
	 * disable the "TX interrupt".
	 */

	if (xmit->head == xmit->tail) {
		uart_disabletxint(serdev);
	}

	/* If any bytes were removed from the buffer, inform any waiters
	 * there there is space available.
	 */

	if (nbytes) {
		serdev->sent(serdev);
	}

	leave_critical_section(flags);
	return nbytes;
}

/************************************************************************************
 * Name: usbclass_sndpacket
 *
 * Description:
 *   This function obtains write requests, transfers the TX data into the request,
 *   and submits the requests to the USB controller.  This continues untils either
 *   (1) there are no further packets available, or (2) thre is not further data
 *   to send.
 *
 ************************************************************************************/

static int usbclass_sndpacket(FAR struct pl2303_dev_s *priv)
{
	FAR struct usbdev_ep_s *ep;
	FAR struct usbdev_req_s *req;
	FAR struct pl2303_req_s *reqcontainer;
	uint16_t reqlen;
	irqstate_t flags;
	int len;
	int ret = OK;

#ifdef CONFIG_DEBUG
	if (priv == NULL) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return -ENODEV;
	}
#endif

	flags = enter_critical_section();

	/* Use our IN endpoint for the transfer */

	ep = priv->epbulkin;

	/* Loop until either (1) we run out or write requests, or (2) usbclass_fillrequest()
	 * is unable to fill the request with data (i.e., until there is no more data
	 * to be sent).
	 */

	uvdbg("head=%d tail=%d nwrq=%d empty=%d\n", priv->serdev.xmit.head, priv->serdev.xmit.tail, priv->nwrq, sq_empty(&priv->reqlist));

	/* Get the maximum number of bytes that will fit into one bulk IN request */

	reqlen = max(CONFIG_PL2303_BULKIN_REQLEN, ep->maxpacket);

	while (!sq_empty(&priv->reqlist)) {
		/* Peek at the request in the container at the head of the list */

		reqcontainer = (struct pl2303_req_s *)sq_peek(&priv->reqlist);
		req = reqcontainer->req;

		/* Fill the request with serial TX data */

		len = usbclass_fillrequest(priv, req->buf, reqlen);
		if (len > 0) {
			/* Remove the empty container from the request list */

			(void)sq_remfirst(&priv->reqlist);
			priv->nwrq--;

			/* Then submit the request to the endpoint */

			req->len = len;
			req->priv = reqcontainer;
			req->flags = USBDEV_REQFLAGS_NULLPKT;
			ret = EP_SUBMIT(ep, req);
			if (ret != OK) {
				usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), (uint16_t)-ret);
				break;
			}
		} else {
			break;
		}
	}

	leave_critical_section(flags);
	return ret;
}

/************************************************************************************
 * Name: usbclass_recvpacket
 *
 * Description:
 *   A normal completion event was received by the read completion handler at the
 *   interrupt level (with interrupts disabled).  This function handles the USB packet
 *   and provides the received data to the uart RX buffer.
 *
 * Assumptions:
 *   Called from the USB interrupt handler with interrupts disabled.
 *
 ************************************************************************************/

static inline int usbclass_recvpacket(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen)
{
	FAR uart_dev_t *serdev = &priv->serdev;
	FAR struct uart_buffer_s *recv = &serdev->recv;
	uint16_t currhead;
	uint16_t nexthead;
	uint16_t nbytes = 0;

	/* Get the next head index. During the time that RX interrupts are disabled, the
	 * the serial driver will be extracting data from the circular buffer and modifying
	 * recv.tail.  During this time, we should avoid modifying recv.head; Instead we will
	 * use a shadow copy of the index.  When interrupts are restored, the real recv.head
	 * will be updated with this indes.
	 */

	if (priv->rxenabled) {
		currhead = recv->head;
	} else {
		currhead = priv->rxhead;
	}

	/* Pre-calculate the head index and check for wrap around.  We need to do this
	 * so that we can determine if the circular buffer will overrun BEFORE we
	 * overrun the buffer!
	 */

	nexthead = currhead + 1;
	if (nexthead >= recv->size) {
		nexthead = 0;
	}

	/* Then copy data into the RX buffer until either: (1) all of the data has been
	 * copied, or (2) the RX buffer is full.  NOTE:  If the RX buffer becomes full,
	 * then we have overrun the serial driver and data will be lost.
	 */

	while (nexthead != recv->tail && nbytes < reqlen) {
		/* Copy one byte to the head of the circular RX buffer */

		recv->buffer[currhead] = *reqbuf++;

		/* Update counts and indices */

		currhead = nexthead;
		nbytes++;

		/* Increment the head index and check for wrap around */

		nexthead = currhead + 1;
		if (nexthead >= recv->size) {
			nexthead = 0;
		}
	}

	/* Write back the head pointer using the shadow index if RX "interrupts"
	 * are disabled.
	 */

	if (priv->rxenabled) {
		recv->head = currhead;
	} else {
		priv->rxhead = currhead;
	}

	/* If data was added to the incoming serial buffer, then wake up any
	 * threads is waiting for incoming data. If we are running in an interrupt
	 * handler, then the serial driver will not run until the interrupt handler
	 * returns.
	 */

	if (priv->rxenabled && nbytes > 0) {
		serdev->received(serdev);
	}

	/* Return an error if the entire packet could not be transferred */

	if (nbytes < reqlen) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RXOVERRUN), 0);
		return -ENOSPC;
	}
	return OK;
}

/****************************************************************************
 * Name: usbclass_allocreq
 *
 * Description:
 *   Allocate a request instance along with its buffer
 *
 ****************************************************************************/

static struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep, uint16_t len)
{
	FAR struct usbdev_req_s *req;

	req = EP_ALLOCREQ(ep);
	if (req != NULL) {
		req->len = len;
		req->buf = EP_ALLOCBUFFER(ep, len);
		if (!req->buf) {
			EP_FREEREQ(ep, req);
			req = NULL;
		}
	}
	return req;
}

/****************************************************************************
 * Name: usbclass_freereq
 *
 * Description:
 *   Free a request instance along with its buffer
 *
 ****************************************************************************/

static void usbclass_freereq(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
	if (ep != NULL && req != NULL) {
		if (req->buf != NULL) {
			EP_FREEBUFFER(ep, req->buf);
		}
		EP_FREEREQ(ep, req);
	}
}

/****************************************************************************
 * Name: usbclass_mkstrdesc
 *
 * Description:
 *   Construct a string descriptor
 *
 ****************************************************************************/

static int usbclass_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc)
{
	const char *str;
	int len;
	int ndata;
	int i;

	switch (id) {
	case 0: {
		/* Descriptor 0 is the language id */

		strdesc->len = 4;
		strdesc->type = USB_DESC_TYPE_STRING;
		strdesc->data[0] = LSBYTE(PL2303_STR_LANGUAGE);
		strdesc->data[1] = MSBYTE(PL2303_STR_LANGUAGE);
		return 4;
	}

	case PL2303_MANUFACTURERSTRID:
		str = CONFIG_PL2303_VENDORSTR;
		break;

	case PL2303_PRODUCTSTRID:
		str = CONFIG_PL2303_PRODUCTSTR;
		break;

	case PL2303_SERIALSTRID:
		str = CONFIG_PL2303_SERIALSTR;
		break;

	case PL2303_CONFIGSTRID:
		str = CONFIG_PL2303_CONFIGSTR;
		break;

	default:
		return -EINVAL;
	}

	/* The string is utf16-le.  The poor man's utf-8 to utf16-le
	 * conversion below will only handle 7-bit en-us ascii
	 */

	len = strlen(str);
	if (len > (PL2303_MAXSTRLEN / 2)) {
		len = (PL2303_MAXSTRLEN / 2);
	}

	for (i = 0, ndata = 0; i < len; i++, ndata += 2) {
		strdesc->data[ndata] = str[i];
		strdesc->data[ndata + 1] = 0;
	}

	strdesc->len = ndata + 2;
	strdesc->type = USB_DESC_TYPE_STRING;
	return strdesc->len;
}

/****************************************************************************
 * Name: usbclass_mkepbulkdesc
 *
 * Description:
 *   Construct the endpoint descriptor
 *
 ****************************************************************************/

#ifdef CONFIG_USBDEV_DUALSPEED
static inline void usbclass_mkepbulkdesc(const FAR struct usb_epdesc_s *indesc, uint16_t mxpacket, FAR struct usb_epdesc_s *outdesc)
{
	/* Copy the canned descriptor */

	memcpy(outdesc, indesc, USB_SIZEOF_EPDESC);

	/* Then add the correct max packet size */

	outdesc->mxpacketsize[0] = LSBYTE(mxpacket);
	outdesc->mxpacketsize[1] = MSBYTE(mxpacket);
}
#endif

/****************************************************************************
 * Name: usbclass_mkcfgdesc
 *
 * Description:
 *   Construct the configuration descriptor
 *
 ****************************************************************************/

#ifdef CONFIG_USBDEV_DUALSPEED
static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type)
#else
static int16_t usbclass_mkcfgdesc(uint8_t *buf)
#endif
{
	FAR struct usb_cfgdesc_s *cfgdesc = (FAR struct usb_cfgdesc_s *)buf;
#ifdef CONFIG_USBDEV_DUALSPEED
	bool hispeed = (speed == USB_SPEED_HIGH);
	uint16_t bulkmxpacket;
#endif
	uint16_t totallen;

	/* This is the total length of the configuration (not necessarily the
	 * size that we will be sending now.
	 */

	totallen = USB_SIZEOF_CFGDESC + USB_SIZEOF_IFDESC + PL2303_NENDPOINTS * USB_SIZEOF_EPDESC;

	/* Configuration descriptor -- Copy the canned descriptor and fill in the
	 * type (we'll also need to update the size below
	 */

	memcpy(cfgdesc, &g_cfgdesc, USB_SIZEOF_CFGDESC);
	buf += USB_SIZEOF_CFGDESC;

	/*  Copy the canned interface descriptor */

	memcpy(buf, &g_ifdesc, USB_SIZEOF_IFDESC);
	buf += USB_SIZEOF_IFDESC;

	/* Make the three endpoint configurations.  First, check for switches
	 * between high and full speed
	 */

#ifdef CONFIG_USBDEV_DUALSPEED
	if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG) {
		hispeed = !hispeed;
	}
#endif

	memcpy(buf, &g_epintindesc, USB_SIZEOF_EPDESC);
	buf += USB_SIZEOF_EPDESC;

#ifdef CONFIG_USBDEV_DUALSPEED
	if (hispeed) {
		bulkmxpacket = 512;
	} else {
		bulkmxpacket = 64;
	}

	usbclass_mkepbulkdesc(&g_epbulkoutdesc, bulkmxpacket, (FAR struct usb_epdesc_s *)buf);
	buf += USB_SIZEOF_EPDESC;
	usbclass_mkepbulkdesc(&g_epbulkindesc, bulkmxpacket, (FAR struct usb_epdesc_s *)buf);
#else
	memcpy(buf, &g_epbulkoutdesc, USB_SIZEOF_EPDESC);
	buf += USB_SIZEOF_EPDESC;
	memcpy(buf, &g_epbulkindesc, USB_SIZEOF_EPDESC);
#endif

	/* Finally, fill in the total size of the configuration descriptor */

	cfgdesc->totallen[0] = LSBYTE(totallen);
	cfgdesc->totallen[1] = MSBYTE(totallen);
	return totallen;
}

/****************************************************************************
 * Name: usbclass_resetconfig
 *
 * Description:
 *   Mark the device as not configured and disable all endpoints.
 *
 ****************************************************************************/

static void usbclass_resetconfig(FAR struct pl2303_dev_s *priv)
{
	/* Are we configured? */

	if (priv->config != PL2303_CONFIGIDNONE) {
		/* Yes.. but not anymore */

		priv->config = PL2303_CONFIGIDNONE;

		/* Inform the "upper half" driver that there is no (functional) USB
		 * connection.
		 */

#ifdef CONFIG_SERIAL_REMOVABLE
		uart_connected(&priv->serdev, false);
#endif

		/* Disable endpoints.  This should force completion of all pending
		 * transfers.
		 */

		EP_DISABLE(priv->epintin);
		EP_DISABLE(priv->epbulkin);
		EP_DISABLE(priv->epbulkout);
	}
}

/****************************************************************************
 * Name: usbclass_setconfig
 *
 * Description:
 *   Set the device configuration by allocating and configuring endpoints and
 *   by allocating and queue read and write requests.
 *
 ****************************************************************************/

static int usbclass_setconfig(FAR struct pl2303_dev_s *priv, uint8_t config)
{
	FAR struct usbdev_req_s *req;
#ifdef CONFIG_USBDEV_DUALSPEED
	struct usb_epdesc_s epdesc;
	uint16_t bulkmxpacket;
#endif
	int i;
	int ret = 0;

#ifdef CONFIG_DEBUG
	if (priv == NULL) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return -EIO;
	}
#endif

	if (config == priv->config) {
		/* Already configured -- Do nothing */

		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0);
		return 0;
	}

	/* Discard the previous configuration data */

	usbclass_resetconfig(priv);

	/* Was this a request to simply discard the current configuration? */

	if (config == PL2303_CONFIGIDNONE) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0);
		return 0;
	}

	/* We only accept one configuration */

	if (config != PL2303_CONFIGID) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0);
		return -EINVAL;
	}

	/* Configure the IN interrupt endpoint */

	ret = EP_CONFIGURE(priv->epintin, &g_epintindesc, false);
	if (ret < 0) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0);
		goto errout;
	}
	priv->epintin->priv = priv;

	/* Configure the IN bulk endpoint */

#ifdef CONFIG_USBDEV_DUALSPEED
	if (priv->usbdev->speed == USB_SPEED_HIGH) {
		bulkmxpacket = 512;
	} else {
		bulkmxpacket = 64;
	}

	usbclass_mkepbulkdesc(&g_epbulkindesc, bulkmxpacket, &epdesc);
	ret = EP_CONFIGURE(priv->epbulkin, &epdesc, false);
#else
	ret = EP_CONFIGURE(priv->epbulkin, &g_epbulkindesc, false);
#endif
	if (ret < 0) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0);
		goto errout;
	}

	priv->epbulkin->priv = priv;

	/* Configure the OUT bulk endpoint */

#ifdef CONFIG_USBDEV_DUALSPEED
	usbclass_mkepbulkdesc(&g_epbulkoutdesc, bulkmxpacket, &epdesc);
	ret = EP_CONFIGURE(priv->epbulkout, &epdesc, true);
#else
	ret = EP_CONFIGURE(priv->epbulkout, &g_epbulkoutdesc, true);
#endif
	if (ret < 0) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0);
		goto errout;
	}

	priv->epbulkout->priv = priv;

	/* Queue read requests in the bulk OUT endpoint */

	DEBUGASSERT(priv->nrdq == 0);
	for (i = 0; i < CONFIG_PL2303_NRDREQS; i++) {
		req = priv->rdreqs[i].req;
		req->callback = usbclass_rdcomplete;
		ret = EP_SUBMIT(priv->epbulkout, req);
		if (ret != OK) {
			usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-ret);
			goto errout;
		}

		priv->nrdq++;
	}

	/* We are successfully configured */

	priv->config = config;

	/* Inform the "upper half" driver that we are "open for business" */

#ifdef CONFIG_SERIAL_REMOVABLE
	uart_connected(&priv->serdev, true);
#endif

	return OK;

errout:
	usbclass_resetconfig(priv);
	return ret;
}

/****************************************************************************
 * Name: usbclass_ep0incomplete
 *
 * Description:
 *   Handle completion of EP0 control operations
 *
 ****************************************************************************/

static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
	if (req->result || req->xfrd != req->len) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT), (uint16_t)-req->result);
	}
}

/****************************************************************************
 * Name: usbclass_rdcomplete
 *
 * Description:
 *   Handle completion of read request on the bulk OUT endpoint.  This
 *   is handled like the receipt of serial data on the "UART"
 *
 ****************************************************************************/

static void usbclass_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
	FAR struct pl2303_dev_s *priv;
	irqstate_t flags;
	int ret;

	/* Sanity check */

#ifdef CONFIG_DEBUG
	if (!ep || !ep->priv || !req) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract references to private data */

	priv = (FAR struct pl2303_dev_s *)ep->priv;

	/* Process the received data unless this is some unusual condition */

	flags = enter_critical_section();
	switch (req->result) {
	case 0:					/* Normal completion */
		usbtrace(TRACE_CLASSRDCOMPLETE, priv->nrdq);
		usbclass_recvpacket(priv, req->buf, req->xfrd);
		break;

	case -ESHUTDOWN:			/* Disconnection */
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0);
		priv->nrdq--;
		leave_critical_section(flags);
		return;

	default:					/* Some other error occurred */
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED), (uint16_t)-req->result);
		break;
	};

	/* Requeue the read request */

	req->len = ep->maxpacket;
	ret = EP_SUBMIT(ep, req);
	if (ret != OK) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-req->result);
	}
	leave_critical_section(flags);
}

/****************************************************************************
 * Name: usbclass_wrcomplete
 *
 * Description:
 *   Handle completion of write request.  This function probably executes
 *   in the context of an interrupt handler.
 *
 ****************************************************************************/

static void usbclass_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
	FAR struct pl2303_dev_s *priv;
	FAR struct pl2303_req_s *reqcontainer;
	irqstate_t flags;

	/* Sanity check */

#ifdef CONFIG_DEBUG
	if (!ep || !ep->priv || !req || !req->priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract references to our private data */

	priv = (FAR struct pl2303_dev_s *)ep->priv;
	reqcontainer = (FAR struct pl2303_req_s *)req->priv;

	/* Return the write request to the free list */

	flags = enter_critical_section();
	sq_addlast((FAR sq_entry_t *)reqcontainer, &priv->reqlist);
	priv->nwrq++;
	leave_critical_section(flags);

	/* Send the next packet unless this was some unusual termination
	 * condition
	 */

	switch (req->result) {
	case OK:					/* Normal completion */
		usbtrace(TRACE_CLASSWRCOMPLETE, priv->nwrq);
		usbclass_sndpacket(priv);
		break;

	case -ESHUTDOWN:			/* Disconnection */
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN), priv->nwrq);
		break;

	default:					/* Some other error occurred */
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED), (uint16_t)-req->result);
		break;
	}
}

/****************************************************************************
 * USB Class Driver Methods
 ****************************************************************************/

/****************************************************************************
 * Name: usbclass_bind
 *
 * Description:
 *   Invoked when the driver is bound to a USB device driver
 *
 ****************************************************************************/

static int usbclass_bind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev)
{
	FAR struct pl2303_dev_s *priv = ((FAR struct pl2303_driver_s *)driver)->dev;
	FAR struct pl2303_req_s *reqcontainer;
	irqstate_t flags;
	uint16_t reqlen;
	int ret;
	int i;

	usbtrace(TRACE_CLASSBIND, 0);

	/* Bind the structures */

	priv->usbdev = dev;

	/* Save the reference to our private data structure in EP0 so that it
	 * can be recovered in ep0 completion events (Unless we are part of
	 * a composite device and, in that case, the composite device owns
	 * EP0).
	 */

	dev->ep0->priv = priv;

	/* Preallocate control request */

	priv->ctrlreq = usbclass_allocreq(dev->ep0, PL2303_MXDESCLEN);
	if (priv->ctrlreq == NULL) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0);
		ret = -ENOMEM;
		goto errout;
	}
	priv->ctrlreq->callback = usbclass_ep0incomplete;

	/* Pre-allocate all endpoints... the endpoints will not be functional
	 * until the SET CONFIGURATION request is processed in usbclass_setconfig.
	 * This is done here because there may be calls to kmm_malloc and the SET
	 * CONFIGURATION processing probably occurrs within interrupt handling
	 * logic where kmm_malloc calls will fail.
	 */

	/* Pre-allocate the IN interrupt endpoint */

	priv->epintin = DEV_ALLOCEP(dev, PL2303_EPINTIN_ADDR, true, USB_EP_ATTR_XFER_INT);
	if (!priv->epintin) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINALLOCFAIL), 0);
		ret = -ENODEV;
		goto errout;
	}
	priv->epintin->priv = priv;

	/* Pre-allocate the IN bulk endpoint */

	priv->epbulkin = DEV_ALLOCEP(dev, PL2303_EPINBULK_ADDR, true, USB_EP_ATTR_XFER_BULK);
	if (!priv->epbulkin) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0);
		ret = -ENODEV;
		goto errout;
	}
	priv->epbulkin->priv = priv;

	/* Pre-allocate the OUT bulk endpoint */

	priv->epbulkout = DEV_ALLOCEP(dev, PL2303_EPOUTBULK_ADDR, false, USB_EP_ATTR_XFER_BULK);
	if (!priv->epbulkout) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0);
		ret = -ENODEV;
		goto errout;
	}
	priv->epbulkout->priv = priv;

	/* Pre-allocate read requests.  The buffer size is one full packet. */

#ifdef CONFIG_USBDEV_DUALSPEED
	reqlen = 512;
#else
	reqlen = 64;
#endif

	for (i = 0; i < CONFIG_PL2303_NRDREQS; i++) {
		reqcontainer = &priv->rdreqs[i];
		reqcontainer->req = usbclass_allocreq(priv->epbulkout, reqlen);
		if (reqcontainer->req == NULL) {
			usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM);
			ret = -ENOMEM;
			goto errout;
		}

		reqcontainer->req->priv = reqcontainer;
		reqcontainer->req->callback = usbclass_rdcomplete;
	}

	/* Pre-allocate write request containers and put in a free list.
	 * The buffer size should be larger than a full packet.  Otherwise,
	 * we will send a bogus null packet at the end of each packet.
	 *
	 * Pick the larger of the max packet size and the configured request
	 * size.
	 */

#ifdef CONFIG_USBDEV_DUALSPEED
	reqlen = 512;
#else
	reqlen = 64;
#endif

	if (CONFIG_PL2303_BULKIN_REQLEN > reqlen) {
		reqlen = CONFIG_CDCACM_BULKIN_REQLEN;
	}

	for (i = 0; i < CONFIG_PL2303_NWRREQS; i++) {
		reqcontainer = &priv->wrreqs[i];
		reqcontainer->req = usbclass_allocreq(priv->epbulkin, reqlen);
		if (reqcontainer->req == NULL) {
			usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM);
			ret = -ENOMEM;
			goto errout;
		}

		reqcontainer->req->priv = reqcontainer;
		reqcontainer->req->callback = usbclass_wrcomplete;

		flags = enter_critical_section();
		sq_addlast((FAR sq_entry_t *)reqcontainer, &priv->reqlist);
		priv->nwrq++;			/* Count of write requests available */
		leave_critical_section(flags);
	}

	/* Report if we are selfpowered */

#ifdef CONFIG_USBDEV_SELFPOWERED
	DEV_SETSELFPOWERED(dev);
#endif

	/* And pull-up the data line for the soft connect function */

	DEV_CONNECT(dev);
	return OK;

errout:
	usbclass_unbind(driver, dev);
	return ret;
}

/****************************************************************************
 * Name: usbclass_unbind
 *
 * Description:
 *    Invoked when the driver is unbound from a USB device driver
 *
 ****************************************************************************/

static void usbclass_unbind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev)
{
	FAR struct pl2303_dev_s *priv;
	FAR struct pl2303_req_s *reqcontainer;
	irqstate_t flags;
	int i;

	usbtrace(TRACE_CLASSUNBIND, 0);

#ifdef CONFIG_DEBUG
	if (!driver || !dev || !dev->ep0) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract reference to private data */

	priv = ((FAR struct pl2303_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG
	if (!priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
		return;
	}
#endif

	/* Make sure that we are not already unbound */

	if (priv != NULL) {
		/* Make sure that the endpoints have been unconfigured.  If
		 * we were terminated gracefully, then the configuration should
		 * already have been reset.  If not, then calling usbclass_resetconfig
		 * should cause the endpoints to immediately terminate all
		 * transfers and return the requests to us (with result == -ESHUTDOWN)
		 */

		usbclass_resetconfig(priv);
		up_mdelay(50);

		/* Free the interrupt IN endpoint */

		if (priv->epintin) {
			DEV_FREEEP(dev, priv->epintin);
			priv->epintin = NULL;
		}

		/* Free the bulk IN endpoint */

		if (priv->epbulkin) {
			DEV_FREEEP(dev, priv->epbulkin);
			priv->epbulkin = NULL;
		}

		/* Free the pre-allocated control request */

		if (priv->ctrlreq != NULL) {
			usbclass_freereq(dev->ep0, priv->ctrlreq);
			priv->ctrlreq = NULL;
		}

		/* Free pre-allocated read requests (which should all have
		 * been returned to the free list at this time -- we don't check)
		 */

		DEBUGASSERT(priv->nrdq == 0);
		for (i = 0; i < CONFIG_PL2303_NRDREQS; i++) {
			reqcontainer = &priv->rdreqs[i];
			if (reqcontainer->req) {
				usbclass_freereq(priv->epbulkout, reqcontainer->req);
				reqcontainer->req = NULL;
			}
		}

		/* Free the bulk OUT endpoint */

		if (priv->epbulkout) {
			DEV_FREEEP(dev, priv->epbulkout);
			priv->epbulkout = NULL;
		}

		/* Free write requests that are not in use (which should be all
		 * of them
		 */

		flags = enter_critical_section();
		DEBUGASSERT(priv->nwrq == CONFIG_PL2303_NWRREQS);
		while (!sq_empty(&priv->reqlist)) {
			reqcontainer = (struct pl2303_req_s *)sq_remfirst(&priv->reqlist);
			if (reqcontainer->req != NULL) {
				usbclass_freereq(priv->epbulkin, reqcontainer->req);
				priv->nwrq--;	/* Number of write requests queued */
			}
		}
		DEBUGASSERT(priv->nwrq == 0);
		leave_critical_section(flags);
	}

	/* Clear out all data in the circular buffer */

	priv->serdev.xmit.head = 0;
	priv->serdev.xmit.tail = 0;
}

/****************************************************************************
 * Name: usbclass_setup
 *
 * Description:
 *   Invoked for ep0 control requests.  This function probably executes
 *   in the context of an interrupt handler.
 *
 ****************************************************************************/

static int usbclass_setup(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev, FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout, size_t outlen)
{
	FAR struct pl2303_dev_s *priv;
	FAR struct usbdev_req_s *ctrlreq;
	uint16_t value;
	uint16_t index;
	uint16_t len;
	int ret = -EOPNOTSUPP;

#ifdef CONFIG_DEBUG
	if (!driver || !dev || !dev->ep0 || !ctrl) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return -EIO;
	}
#endif

	/* Extract reference to private data */

	usbtrace(TRACE_CLASSSETUP, ctrl->req);
	priv = ((FAR struct pl2303_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG
	if (!priv || !priv->ctrlreq) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
		return -ENODEV;
	}
#endif
	ctrlreq = priv->ctrlreq;

	/* Extract the little-endian 16-bit values to host order */

	value = GETUINT16(ctrl->value);
	index = GETUINT16(ctrl->index);
	len = GETUINT16(ctrl->len);

	uvdbg("type=%02x req=%02x value=%04x index=%04x len=%04x\n", ctrl->type, ctrl->req, value, index, len);

	switch (ctrl->type & USB_REQ_TYPE_MASK) {
	/***********************************************************************
	 * Standard Requests
	 ***********************************************************************/

	case USB_REQ_TYPE_STANDARD: {
		switch (ctrl->req) {
		case USB_REQ_GETDESCRIPTOR: {
			/* The value field specifies the descriptor type in the MS byte and the
			 * descriptor index in the LS byte (order is little endian)
			 */

			switch (ctrl->value[1]) {
			case USB_DESC_TYPE_DEVICE: {
				ret = USB_SIZEOF_DEVDESC;
				memcpy(ctrlreq->buf, &g_devdesc, ret);
			}
			break;

#ifdef CONFIG_USBDEV_DUALSPEED
			case USB_DESC_TYPE_DEVICEQUALIFIER: {
				ret = USB_SIZEOF_QUALDESC;
				memcpy(ctrlreq->buf, &g_qualdesc, ret);
			}
			break;

			case USB_DESC_TYPE_OTHERSPEEDCONFIG:
#endif							/* CONFIG_USBDEV_DUALSPEED */

			case USB_DESC_TYPE_CONFIG: {
#ifdef CONFIG_USBDEV_DUALSPEED
				ret = usbclass_mkcfgdesc(ctrlreq->buf, dev->speed, ctrl->req);
#else
				ret = usbclass_mkcfgdesc(ctrlreq->buf);
#endif
			}
			break;

			case USB_DESC_TYPE_STRING: {
				/* index == language code. */

				ret = usbclass_mkstrdesc(ctrl->value[0], (struct usb_strdesc_s *)ctrlreq->buf);
			}
			break;

			default: {
				usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC), value);
			}
			break;
			}
		}
		break;

		case USB_REQ_SETCONFIGURATION: {
			if (ctrl->type == 0) {
				ret = usbclass_setconfig(priv, value);
			}
		}
		break;

		case USB_REQ_GETCONFIGURATION: {
			if (ctrl->type == USB_DIR_IN) {
				*(FAR uint8_t *)ctrlreq->buf = priv->config;
				ret = 1;
			}
		}
		break;

		case USB_REQ_SETINTERFACE: {
			if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE) {
				if (priv->config == PL2303_CONFIGID && index == PL2303_INTERFACEID && value == PL2303_ALTINTERFACEID) {
					usbclass_resetconfig(priv);
					usbclass_setconfig(priv, priv->config);
					ret = 0;
				}
			}
		}
		break;

		case USB_REQ_GETINTERFACE: {
			if (ctrl->type == (USB_DIR_IN | USB_REQ_RECIPIENT_INTERFACE) && priv->config == PL2303_CONFIGIDNONE) {
				if (index != PL2303_INTERFACEID) {
					ret = -EDOM;
				} else {
					*(FAR uint8_t *)ctrlreq->buf = PL2303_ALTINTERFACEID;
					ret = 1;
				}
			}
		}
		break;

		default:
			usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ), ctrl->req);
			break;
		}
	}
	break;

	/***********************************************************************
	 * PL2303 Vendor-Specific Requests
	 ***********************************************************************/

	case PL2303_CONTROL_TYPE: {
		if ((ctrl->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE) {
			switch (ctrl->req) {
			case PL2303_SETLINEREQUEST: {
				memcpy(priv->linest, ctrlreq->buf, min(len, 7));
				ret = 0;
			}
			break;

			case PL2303_GETLINEREQUEST: {
				memcpy(ctrlreq->buf, priv->linest, 7);
				ret = 7;
			}
			break;

			case PL2303_SETCONTROLREQUEST:
			case PL2303_BREAKREQUEST: {
				ret = 0;
			}
			break;

			default:
				usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type);
				break;
			}
		}
	}
	break;

	case PL2303_RWREQUEST_TYPE: {
		if ((ctrl->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE) {
			if (ctrl->req == PL2303_RWREQUEST) {
				if ((ctrl->type & USB_DIR_IN) != 0) {
					*(FAR uint32_t *)ctrlreq->buf = 0xdeadbeef;
					ret = 4;
				} else {
					ret = 0;
				}
			} else {
				usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type);
			}
		}
	}
	break;

	default:
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE), ctrl->type);
		break;
	}

	/* Respond to the setup command if data was returned.  On an error return
	 * value (ret < 0), the USB driver will stall.
	 */

	if (ret >= 0) {
		ctrlreq->len = min(len, ret);
		ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;
		ret = EP_SUBMIT(dev->ep0, ctrlreq);
		if (ret < 0) {
			usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret);
			ctrlreq->result = OK;
			usbclass_ep0incomplete(dev->ep0, ctrlreq);
		}
	}

	return ret;
}

/****************************************************************************
 * Name: usbclass_disconnect
 *
 * Description:
 *   Invoked after all transfers have been stopped, when the host is
 *   disconnected.  This function is probably called from the context of an
 *   interrupt handler.
 *
 ****************************************************************************/

static void usbclass_disconnect(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev)
{
	FAR struct pl2303_dev_s *priv;
	irqstate_t flags;

	usbtrace(TRACE_CLASSDISCONNECT, 0);

#ifdef CONFIG_DEBUG
	if (!driver || !dev || !dev->ep0) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract reference to private data */

	priv = ((FAR struct pl2303_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG
	if (!priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
		return;
	}
#endif

	/* Inform the "upper half serial driver that we have lost the USB serial
	 * connection.
	 */

	flags = enter_critical_section();
#ifdef CONFIG_SERIAL_REMOVABLE
	uart_connected(&priv->serdev, false);
#endif

	/* Reset the configuration */

	usbclass_resetconfig(priv);

	/* Clear out all outgoing data in the circular buffer */

	priv->serdev.xmit.head = 0;
	priv->serdev.xmit.tail = 0;
	priv->rxhead = 0;
	leave_critical_section(flags);

	/* Perform the soft connect function so that we will we can be
	 * re-enumerated.
	 */

	DEV_CONNECT(dev);
}

/****************************************************************************
 * Name: usbclass_suspend
 *
 * Description:
 *   Handle the USB suspend event.
 *
 ****************************************************************************/

#ifdef CONFIG_SERIAL_REMOVABLE
static void usbclass_suspend(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev)
{
	FAR struct pl2303_dev_s *priv;

	usbtrace(TRACE_CLASSSUSPEND, 0);

#ifdef CONFIG_DEBUG
	if (!driver || !dev) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract reference to private data */

	priv = ((FAR struct pl2303_driver_s *)driver)->dev;

	/* And let the "upper half" driver now that we are suspended */

	uart_connected(&priv->serdev, false);
}
#endif

/****************************************************************************
 * Name: usbclass_resume
 *
 * Description:
 *   Handle the USB resume event.
 *
 ****************************************************************************/

#ifdef CONFIG_SERIAL_REMOVABLE
static void usbclass_resume(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev)
{
	FAR struct pl2303_dev_s *priv;

	usbtrace(TRACE_CLASSRESUME, 0);

#ifdef CONFIG_DEBUG
	if (!driver || !dev) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract reference to private data */

	priv = ((FAR struct pl2303_driver_s *)driver)->dev;

	/* Are we still configured? */

	if (priv->config != PL2303_CONFIGIDNONE) {
		/* Yes.. let the "upper half" know that have resumed */

		uart_connected(&priv->serdev, true);
	}
}
#endif

/****************************************************************************
 * Serial Device Methods
 ****************************************************************************/

/****************************************************************************
 * Name: usbser_setup
 *
 * Description:
 *   This method is called the first time that the serial port is opened.
 *
 ****************************************************************************/

static int usbser_setup(FAR struct uart_dev_s *dev)
{
	FAR struct pl2303_dev_s *priv;

	usbtrace(PL2303_CLASSAPI_SETUP, 0);

	/* Sanity check */

#ifdef CONFIG_DEBUG
	if (!dev || !dev->priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return -EIO;
	}
#endif

	/* Extract reference to private data */

	priv = (FAR struct pl2303_dev_s *)dev->priv;

	/* Check if we have been configured */

	if (priv->config == PL2303_CONFIGIDNONE) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SETUPNOTCONNECTED), 0);
		return -ENOTCONN;
	}

	return OK;
}

/****************************************************************************
 * Name: usbser_shutdown
 *
 * Description:
 *   This method is called when the serial port is closed.  This operation
 *   is very simple for the USB serial backend because the serial driver
 *   has already assured that the TX data has full drained -- it calls
 *   usbser_txempty() until that function returns true before calling this
 *   function.
 *
 ****************************************************************************/

static void usbser_shutdown(FAR struct uart_dev_s *dev)
{
	usbtrace(PL2303_CLASSAPI_SHUTDOWN, 0);

	/* Sanity check */

#ifdef CONFIG_DEBUG
	if (!dev || !dev->priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
	}
#endif
}

/****************************************************************************
 * Name: usbser_attach
 *
 * Description:
 *   Does not apply to the USB serial class device
 *
 ****************************************************************************/

static int usbser_attach(FAR struct uart_dev_s *dev)
{
	usbtrace(PL2303_CLASSAPI_ATTACH, 0);
	return OK;
}

/****************************************************************************
 * Name: usbser_detach
 *
 * Description:
 *   Does not apply to the USB serial class device
 *
 ****************************************************************************/

static void usbser_detach(FAR struct uart_dev_s *dev)
{
	usbtrace(PL2303_CLASSAPI_DETACH, 0);
}

/****************************************************************************
 * Name: usbser_rxint
 *
 * Description:
 *   Called by the serial driver to enable or disable RX interrupts.  We, of
 *   course, have no RX interrupts but must behave consistently.  This method
 *   is called under the conditions:
 *
 *   1. With enable==true when the port is opened (just after usbser_setup
 *      and usbser_attach are called called)
 *   2. With enable==false while transferring data from the RX buffer
 *   2. With enable==true while waiting for more incoming data
 *   3. With enable==false when the port is closed (just before usbser_detach
 *      and usbser_shutdown are called).
 *
 ****************************************************************************/

static void usbser_rxint(FAR struct uart_dev_s *dev, bool enable)
{
	FAR struct pl2303_dev_s *priv;
	FAR uart_dev_t *serdev;
	irqstate_t flags;

	usbtrace(PL2303_CLASSAPI_RXINT, (uint16_t)enable);

	/* Sanity check */

#ifdef CONFIG_DEBUG
	if (!dev || !dev->priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract reference to private data */

	priv = (FAR struct pl2303_dev_s *)dev->priv;
	serdev = &priv->serdev;

	/* We need exclusive access to the RX buffer and private structure
	 * in the following.
	 */

	flags = enter_critical_section();
	if (enable) {
		/* RX "interrupts" are enabled.  Is this a transition from disabled
		 * to enabled state?
		 */

		if (!priv->rxenabled) {
			/* Yes.  During the time that RX interrupts are disabled, the
			 * the serial driver will be extracting data from the circular
			 * buffer and modifying recv.tail.  During this time, we
			 * should avoid modifying recv.head; When interrupts are restored,
			 * we can update the head pointer for all of the data that we
			 * put into cicular buffer while "interrupts" were disabled.
			 */

			if (priv->rxhead != serdev->recv.head) {
				serdev->recv.head = priv->rxhead;

				/* Yes... signal the availability of new data */

				serdev->received(serdev);
			}

			/* RX "interrupts are no longer disabled */

			priv->rxenabled = true;
		}
	}

	/* RX "interrupts" are disabled.  Is this a transition from enabled
	 * to disabled state?
	 */

	else if (priv->rxenabled) {
		/* Yes.  During the time that RX interrupts are disabled, the
		 * the serial driver will be extracting data from the circular
		 * buffer and modifying recv.tail.  During this time, we
		 * should avoid modifying recv.head; When interrupts are disabled,
		 * we use a shadow index and continue adding data to the circular
		 * buffer.
		 */

		priv->rxhead = serdev->recv.head;
		priv->rxenabled = false;
	}
	leave_critical_section(flags);
}

/****************************************************************************
 * Name: usbser_txint
 *
 * Description:
 *   Called by the serial driver to enable or disable TX interrupts.  We, of
 *   course, have no TX interrupts but must behave consistently.  Initially,
 *   TX interrupts are disabled.  This method is called under the conditions:
 *
 *   1. With enable==false while transferring data into the TX buffer
 *   2. With enable==true when data may be taken from the buffer.
 *   3. With enable==false when the TX buffer is empty
 *
 ****************************************************************************/

static void usbser_txint(FAR struct uart_dev_s *dev, bool enable)
{
	FAR struct pl2303_dev_s *priv;

	usbtrace(PL2303_CLASSAPI_TXINT, (uint16_t)enable);

	/* Sanity checks */

#ifdef CONFIG_DEBUG
	if (!dev || !dev->priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return;
	}
#endif

	/* Extract references to private data */

	priv = (FAR struct pl2303_dev_s *)dev->priv;

	/* If the new state is enabled and if there is data in the XMIT buffer,
	 * send the next packet now.
	 */

	uvdbg("enable=%d head=%d tail=%d\n", enable, priv->serdev.xmit.head, priv->serdev.xmit.tail);

	if (enable && priv->serdev.xmit.head != priv->serdev.xmit.tail) {
		usbclass_sndpacket(priv);
	}
}

/****************************************************************************
 * Name: usbser_txempty
 *
 * Description:
 *   Return true when all data has been sent.  This is called from the
 *   serial driver when the driver is closed.  It will call this API
 *   periodically until it reports true.  NOTE that the serial driver takes all
 *   responsibility for flushing TX data through the hardware so we can be
 *   a bit sloppy about that.
 *
 ****************************************************************************/

static bool usbser_txempty(FAR struct uart_dev_s *dev)
{
	FAR struct pl2303_dev_s *priv = (FAR struct pl2303_dev_s *)dev->priv;

	usbtrace(PL2303_CLASSAPI_TXEMPTY, 0);

#ifdef CONFIG_DEBUG
	if (!priv) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
		return true;
	}
#endif

	/* When all of the allocated write requests have been returned to the
	 * reqlist, then there is no longer any TX data in flight.
	 */

	return priv->nwrq >= CONFIG_PL2303_NWRREQS;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: usbdev_serialinitialize
 *
 * Description:
 *   Register USB serial port (and USB serial console if so configured).
 *
 ****************************************************************************/

int usbdev_serialinitialize(int minor)
{
	FAR struct pl2303_alloc_s *alloc;
	FAR struct pl2303_dev_s *priv;
	FAR struct pl2303_driver_s *drvr;
	char devname[16];
	int ret;

	/* Allocate the structures needed */

	alloc = (FAR struct pl2303_alloc_s *)kmm_malloc(sizeof(struct pl2303_alloc_s));
	if (!alloc) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0);
		return -ENOMEM;
	}

	/* Convenience pointers into the allocated blob */

	priv = &alloc->dev;
	drvr = &alloc->drvr;

	/* Initialize the USB serial driver structure */

	memset(priv, 0, sizeof(struct pl2303_dev_s));
	sq_init(&priv->reqlist);

	/* Fake line status */

	priv->linest[0] = (115200) & 0xff;	/* Baud=115200 */
	priv->linest[1] = (115200 >> 8) & 0xff;
	priv->linest[2] = (115200 >> 16) & 0xff;
	priv->linest[3] = (115200 >> 24) & 0xff;
	priv->linest[4] = 0;		/* One stop bit */
	priv->linest[5] = 0;		/* No parity */
	priv->linest[6] = 8;		/* 8 data bits */

	/* Initialize the serial driver sub-structure */

#ifdef CONFIG_SERIAL_REMOVABLE
	priv->serdev.disconnected = true;
#endif
	priv->serdev.recv.size = CONFIG_PL2303_RXBUFSIZE;
	priv->serdev.recv.buffer = priv->rxbuffer;
	priv->serdev.xmit.size = CONFIG_PL2303_TXBUFSIZE;
	priv->serdev.xmit.buffer = priv->txbuffer;
	priv->serdev.ops = &g_uartops;
	priv->serdev.priv = priv;

	/* Initialize the USB class driver structure */

#ifdef CONFIG_USBDEV_DUALSPEED
	drvr->drvr.speed = USB_SPEED_HIGH;
#else
	drvr->drvr.speed = USB_SPEED_FULL;
#endif
	drvr->drvr.ops = &g_driverops;
	drvr->dev = priv;

	/* Register the USB serial class driver */

	ret = usbdev_register(&drvr->drvr);
	if (ret) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER), (uint16_t)-ret);
		goto errout_with_alloc;
	}

	/* Register the USB serial console */

#ifdef CONFIG_PL2303_CONSOLE
	priv->serdev.isconsole = true;
	ret = uart_register("/dev/console", &priv->serdev);
	if (ret < 0) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONSOLEREGISTER), (uint16_t)-ret);
		goto errout_with_class;
	}
#endif

	/* Register the single port supported by this implementation */

	snprintf(devname, sizeof(devname), "/dev/ttyUSB%d", minor);
	ret = uart_register(devname, &priv->serdev);
	if (ret) {
		usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTREGISTER), (uint16_t)-ret);
		goto errout_with_class;
	}
	return OK;

errout_with_class:
	usbdev_unregister(&drvr->drvr);
errout_with_alloc:
	kmm_free(alloc);
	return ret;
}
